/**Filename: main.c
 * Author(s): Vladimir Dudnic, Michael Groenier, Derek Fleener
 * Winter 2013 TCSS422
 * Project 2
 */

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include"queue/blockingQueue.h"
#include"process/process.h"
#include"process/instruction.h"
#include"scheduler/scheduler.h"
#include"arraylist/arrayList.h"
#include<unistd.h>
#include<termios.h>


#define MEM_LOCATIONS 30
#define MEM_PRO_CON_START 20
#define MAX_PRO_CONS

void checkMemoryQueues();
void* interrupt(void*);
void* keyboardLoop(void*);
void* joystickLoop(void*);

void handleInterrupt(int);


blockingQueue readyQueue;
blockingQueue keyboardQueue;
blockingQueue joystickQueue;

pthread_t keyboardThread;
pthread_t joystickThread;
pthread_t systemTimer;


int done = 0;

int sharedMemory[MEM_LOCATIONS];

queue memoryQueue[MEM_LOCATIONS];

Scheduler* sched;

int interrupted = 0;

int main(int argc, char** argv){

  Process* currentProcess;
  Instruction currentInstruction;
  int current_pro_con = MEM_PRO_CON_START;
  int i = 0;
  int blocked = 0;
  sched = (Scheduler*)malloc(sizeof(Scheduler));

  for(i = 0; i < MEM_LOCATIONS; i++){
    queue_init(&memoryQueue[i], 1);
  }

  if(argc == 1){ //check to make sure there is some input
    printf("**** No input for arguments ****\n");
    printf("Starting with PRO/CONS ** 1 UI ** 1 CALC ** Priority Scheduling \n");
    schedulerInit(sched, PRIORITY);

    sched->readyProcess(sched, createUI());
    sched->readyProcess(sched, createCalc());
    sched->readyProcess(sched, createCons(20));
    sched->readyProcess(sched, createProd(20));

  }else{  //there was some input
    //switch on the first arg.
    switch(argv[1][0]){
    case 'p':
      schedulerInit(sched, PRIORITY);
      printf("Started Priority scheduling.\n");
      break;
    case 'l':
      schedulerInit(sched, LOTTERY);
      printf("Started Lottery scheduling\n");
      break;
    case 'r':
      schedulerInit(sched, ROUND_ROBIN);
      printf("Started Round Robin scheduling\n");
      break;
    }

    for(i = 0; i < strlen(argv[2]);i++){
        switch(argv[2][i]){
        case 'u':
          sched->readyProcess(sched, createUI());
          printf("Made a ui process\n");
          break;
        case 'c':
          sched->readyProcess(sched, createCalc());
          printf("Made a Calc process\n");
          break;
        case 'p':
          sched->readyProcess(sched, createProd(current_pro_con));
          sched->readyProcess(sched, createCons(current_pro_con++));
          printf("Made a Producer and a Consumer \n");
          break;
      }
   }

  }


  blockingQueue_init(&keyboardQueue);
  blockingQueue_init(&joystickQueue);

  pthread_create(&keyboardThread, NULL, &keyboardLoop, NULL);
  pthread_create(&joystickThread, NULL, &joystickLoop, NULL);
  pthread_create(&systemTimer, NULL, &interrupt, NULL);

  while(!done){
    currentProcess = sched->nextProcess(sched);
    currentProcess->s = RUNNING;
    printf("\n---------------------------------------------------------");
    printf("\nRunning Process: %s", processTypeToString(currentProcess->t));
    fflush(stdout);

    while(!interrupted && !blocked){
      currentInstruction = processNextInstruction(currentProcess);

      switch(currentInstruction.type){
      case IO_REQ:
        switch(currentInstruction.arg){
          case KEYBOARD:
            currentProcess->s = BLOCKED;
            printf("\nProcess Blocking for KeyBoard IO: %s", processTypeToString(currentProcess->t));
            blockingQueue_enqueue(&keyboardQueue, currentProcess);
            blocked = 1;
            break;
          case JOYSTICK:
            currentProcess->s = BLOCKED;
            printf("\nProcess Blocking for Joystick IO: %s", processTypeToString(currentProcess->t));
            blockingQueue_enqueue(&joystickQueue, currentProcess);
            blocked = 1;
            break;
        }
        break;
      case MEM_UP:
        printf("\nProcess Upped MEM ADDR (%d): %s", currentInstruction.arg, processTypeToString(currentProcess->t));
        sharedMemory[currentInstruction.arg]++;
        break;
      case MEM_DN:

        if(sharedMemory[currentInstruction.arg] <= 0){

          currentProcess->s = BLOCKED;
          printf("\nProcess Blocking for MEM ADDR (%d): %s", currentInstruction.arg, processTypeToString(currentProcess->t));
          queue_enqueue(&memoryQueue[currentInstruction.arg], currentProcess);

          blocked = 1;

        } else {
          //do the down.
          sharedMemory[currentInstruction.arg]--;
        }

        break;
      case DUMB_INST:
        //printf("\nProcess did nothing: %s", procTypeToString(current_proc->t));
        break;
      }
      sleep(1);
    }

    if(blocked == 1){
      blocked = 0;
    } else {
      interrupted = 0;
      currentProcess->s = RUNNABLE;
      sched->readyProcess(sched, currentProcess);
      printf("\nProcess interrupted: %s", processTypeToString(currentProcess->t));
    }

    checkMemoryQueues();
  }

  return EXIT_SUCCESS;
}

void checkMemoryQueues(){
  int i = 0;
  Process* temp;

  for(i = 0; i < MEM_LOCATIONS; i++){
    if(sharedMemory[i] > 0){

      temp = (Process*)queue_dequeue(&memoryQueue[i]);
      if(temp != NULL){

        sched->readyProcess(sched, temp);
        printf("\nSet process to Ready: %s", processTypeToString(temp->t));
        sharedMemory[i]--;
      }
    }
  }
}

/**
 * The system Timer.
 */
void* interrupt(void* arg){
  while(!done){
    sleep(5);
    interrupted = 1;
  }
}

void* keyboardLoop(void* arg){
  char c = 0;
  //make this stop when I change a var.
  while(!done){
    c = getch();
    if(c == '.'){
      done = 1;
    }
    printf("\nReceived interrupt from keyboard.");
    handleInterrupt(KEYBOARD);
  }
}

void* joystickLoop(void* arg){
  while(!done){
    sleep(10);
    printf("\nReceived interrupt from joystick.");
    handleInterrupt(JOYSTICK);
  }
}
void handleInterrupt(int the_source){
  Process* temp;
  switch(the_source){
  case KEYBOARD:
    temp = (Process*)blockingQueue_dequeue(&keyboardQueue);
    if(temp != NULL)
      printf("About to ready from KB: %s", processTypeToString(temp->t));
    break;
  case JOYSTICK:
    temp = (Process*)blockingQueue_dequeue(&joystickQueue);
    if(temp != NULL)
      printf("About to ready from JOY: %s", processTypeToString(temp->t));
    break;
  }
  if(temp != NULL){

    sched->readyProcess(sched, temp);
  }
}

int getch() {
    struct termios term_attr;
    tcflag_t old_c_lflag;
    int c;

    tcgetattr(STDIN_FILENO, &term_attr);
    old_c_lflag = term_attr.c_lflag;
    term_attr.c_lflag &= ~(ICANON|ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &term_attr);
    c = getchar();
    term_attr.c_lflag = old_c_lflag;
    tcsetattr(STDIN_FILENO, TCSANOW, &term_attr);
    return c;
}

